{
 "cells": [
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 特征匹配",
   "id": "e5ad05f4098d8b29"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### Brute-Force蛮力匹配",
   "id": "2a6f6b7a594cbc0c"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-20T17:37:54.796478Z",
     "start_time": "2025-09-20T17:37:54.595128Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "img1 = cv2.imread('data/box.png', 0)\n",
    "img2 = cv2.imread('data/box_in_scene.png', 0)\n",
    "\n",
    "def cv_show(img, name='Image'):\n",
    "    cv2.imshow(name, img)\n",
    "    cv2.waitKey(0)\n",
    "    cv2.destroyAllWindows()\n",
    "\n",
    "sift = cv2.xfeatures2d.SIFT_create()\n",
    "kp1, des1 = sift.detectAndCompute(img1, None)\n",
    "kp2, des2 = sift.detectAndCompute(img2, None)"
   ],
   "id": "d88e6a35a2a8a3a",
   "outputs": [],
   "execution_count": 3
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-20T17:38:34.074466Z",
     "start_time": "2025-09-20T17:38:28.139538Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# crossCheck表示两个特征点要相互匹配， 例如A中的第i个特征点与B中的第i个特征点匹配，那么B中的第j个特征点与A中的第j个特征点匹配。\n",
    "# NORM_L2: 归一化数组的(欧几里德距离)\n",
    "bf = cv2.BFMatcher(crossCheck=True)\n",
    "# 1对1匹配\n",
    "matches = bf.match(des1, des2)\n",
    "matches = sorted(matches, key=lambda x: x.distance)\n",
    "img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches[:10], None, flags=2)\n",
    "cv_show(img3)"
   ],
   "id": "720481cd2559db69",
   "outputs": [],
   "execution_count": 4
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-20T17:40:36.551377Z",
     "start_time": "2025-09-20T17:40:32.494754Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# k对最佳匹配\n",
    "bf = cv2.BFMatcher()\n",
    "matches = bf.knnMatch(des1, des2, k=2)\n",
    "good = []\n",
    "for m, n in matches:\n",
    "    if m.distance < 0.75 * n.distance:\n",
    "        good.append([m])\n",
    "\n",
    "img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=2)\n",
    "cv_show(img3)"
   ],
   "id": "a614042872a9f523",
   "outputs": [],
   "execution_count": 5
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 随机抽样一致算法 (Random sample consensus, RANSAC)",
   "id": "dc7debb42461cfe9"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "![title](data/ransac_1.png)",
   "id": "9bac967803f0b1f8"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "选择最初样本点进行拟合，给定一个容忍范围，不断进行迭代",
   "id": "52f942c6a5352166"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "![title](data/ransac_2.png)",
   "id": "22b366636c5f9999"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "每次拟合后，容忍范围内都有对应的数据点数，找出数据点数最多的情况，就是最终的拟合结果",
   "id": "d7dc6eea1dc8c78b"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "![title](data/ransac_3.png)",
   "id": "cfa77dde184e2e2b"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "单应性矩阵",
   "id": "be291fe785176b74"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "![title](data/ransac_4.png)",
   "id": "3fcc460f17477e9c"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-20T17:48:16.249914Z",
     "start_time": "2025-09-20T17:48:16.199120Z"
    }
   },
   "cell_type": "code",
   "source": [
    "bf = cv2.BFMatcher()\n",
    "matches = bf.knnMatch(des1, des2, k=2)\n",
    "good = []\n",
    "for m, n in matches:\n",
    "    if m.distance < 0.75 * n.distance:\n",
    "        good.append(m)\n",
    "\n",
    "# 提取匹配点的坐标\n",
    "src_pts = np.float32([kp1[m.queryIdx].pt for m in good])\n",
    "dst_pts = np.float32([kp2[m.trainIdx].pt for m in good])\n",
    "\n",
    "# 使用 RANSAC 估计单应性矩阵\n",
    "H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)\n",
    "\n",
    "# mask 表示哪些是内点（正确匹配）\n",
    "inliers_mask = mask.ravel().tolist()\n",
    "print(f\"总匹配数: {len(good)}, 内点数: {sum(inliers_mask)}\")\n",
    "\n",
    "# 只绘制内点匹配\n",
    "matches_ransac = [good[i] for i in range(len(good)) if inliers_mask[i]]\n",
    "img_ransac = cv2.drawMatches(img1, kp1, img2, kp2, matches_ransac, None, flags=2)"
   ],
   "id": "ef214a6611b9235d",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "总匹配数: 80, 内点数: 75\n"
     ]
    }
   ],
   "execution_count": 6
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "3b897a4fc3d8f1ff"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
